package com.zcy.gift.service.impl;

import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.zcy.gift.constants.BaseConstants;
import com.zcy.gift.domain.TenantType;
import com.zcy.gift.mapper.TenantTypeMapper;
import com.zcy.gift.service.ITenantTypeService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zcy
 * @since 2024-01-24
 */
@Service
public class TenantTypeServiceImpl extends ServiceImpl<TenantTypeMapper, TenantType> implements ITenantTypeService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    @Override
    public List<TenantType> selectList(Wrapper<TenantType> wrapper) {
        Object object = redisTemplate.opsForValue().get(BaseConstants.TENANT_TYPE_IN_REDIS);
        if (Objects.nonNull(object)) {
            return (List<TenantType>)object;
        }

        List<TenantType> tenantTypes = null;
        RLock lock = redissonClient.getLock(BaseConstants.TENANT_TYPE_IN_REDIS);
        try {
            lock.lock();
            object = redisTemplate.opsForValue().get(BaseConstants.TENANT_TYPE_IN_REDIS);
            if (Objects.nonNull(object)) {
                return (List<TenantType>)object;
            }
            tenantTypes = super.selectList(wrapper);
            if (tenantTypes != null && tenantTypes.size() > 0) {
                redisTemplate.opsForValue().set(BaseConstants.TENANT_TYPE_IN_REDIS, tenantTypes);
            } else {
                // 如果数据库中没有数据,就缓存默认值，解决缓存穿透问题
                TenantType tenantType = new TenantType();
                redisTemplate.opsForValue().set(BaseConstants.TENANT_TYPE_IN_REDIS, tenantType);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
        return tenantTypes;
    }



/**
 * 当对数据库的数据做增删改时,要同步修改缓存中的数据,否则会造成缓存和数据库数据不一致
 * 实现: 修改数据库后删除缓存
 * <p>
 * 并发情况下,redis数据和数据库数据有可能不一致,读写同时操作时,有可能将旧数据写入到了缓存中
 * 解决方案:
 * <p>
 * 方案一: 不解决,有些公司不解决该问题,是因为有部分人认为:读操作一定比写操作快,所以不会产生该问题,因此不解决
 *         但是不一定,因为如果读数据库结束以后,可以做一些额外的操作
 * <p>
 * 方案二:  同步延迟双删  第一次删除缓存以后,睡眠一会儿以后再删除一次缓存
 *       缺点: 非常影响性能,每次修改都要等待一会儿以后,才会做第二次删除,才会给前端做响应
 *       异步延迟双删
 *       不解决,因为这个功能是给平台管理员用的,所以慢一点就慢一点
 * <p>
 * 方案三: 异步延迟双删, 第一次删除缓存以后,另外单开一个线程做第二次缓存删除
 *        可以解决同步延迟双删的性能问题
 *        如果1000个请求同时过来,就会创建1000个线程来执行延迟双删,对于系统来说压力就太大了
 *           如何解决: 线程池  如果第二次延迟删除失败怎么办??如果前面等待的线程太多了,阻塞或者没有执行就报错了
 *                    rocketmq 延迟消息(等待一会儿以后再执行第二次)+事务消息(保证如果数据库操作和第一次删除成功,那么第二次删除也必须要执行)+重试
 * <p>
 *  方案四: rocketmq事务消息+重试+sleep()
 **/

    @Override
    @Transactional
    public boolean insert(TenantType entity) {
        boolean insert = super.insert(entity);
        // 同步延迟双删
//        synchronousDelayedDoubleDeletion();
        // 异步延迟双删
        asynchronousDelayedDoubleDeletion();
        return insert;

    }

    @Override
    @Transactional
    public boolean deleteById(Serializable id) {
        boolean delete = super.deleteById(id);
        // 同步延迟双删
//        synchronousDelayedDoubleDeletion();
        // 异步延迟双删
        asynchronousDelayedDoubleDeletion();
        return delete;
    }

    @Override
    @Transactional
    public boolean update(TenantType entity, Wrapper<TenantType> wrapper) {
        boolean update = super.update(entity, wrapper);
        // 同步延迟双删
        synchronousDelayedDoubleDeletion();
        // 异步延迟双删
//        asynchronousDelayedDoubleDeletion();
        return update;
    }

    /**
     * 同步延迟双删
     */
    private void synchronousDelayedDoubleDeletion() {
        redisTemplate.delete(BaseConstants.TENANT_TYPE_IN_REDIS);
        System.out.println("第一次删除缓存");
        try {
            Thread.sleep(3000);
            redisTemplate.delete(BaseConstants.TENANT_TYPE_IN_REDIS);
            System.out.println("第二次删除缓存");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步延迟双删
     */
    private void asynchronousDelayedDoubleDeletion() {
        redisTemplate.delete(BaseConstants.TENANT_TYPE_IN_REDIS);
        System.out.println("第一次删除缓存");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    redisTemplate.delete(BaseConstants.TENANT_TYPE_IN_REDIS);
                    System.out.println("第二次删除缓存");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
